<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>8.3.1-正确地设置元素属性</title>
</head>

<body>
    <div id="foo">
        <input value="foo" id="my-input" type="text" value="foo" class="foo" onchange="change()" />
    </div>
</body>
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        /**
         * 原本需要浏览器来完成的工作，现在需要框架来完成。
         * - 普通的 HTML 文件来说，当浏览器解析 HTML 代码后，
         *   会自动分析 HTML Attributes 并设置合适的 DOM Properties
         * - 编写在 Vue.js 的单文件组件中的模板不会被浏览器解析
         * 
         * 使用 setAttribute 函数设置的值总是会被字符串化
         * - 按钮来说，它的 el.disabled 属性值是布尔类型的，
         *   并且它不关心具体的HTML Attributes 的值是什么，
         *   只要 disabled 属性存在，按钮就会被禁用
         * - <button :disabled="false">Button</button>
         *   el.setAttribute('disabled', false)  ==>  el.setAttribute('disabled', 'false')
         * 
         * 设置 DOM Properties
         * - 由于 el.disabled 是布尔类型的值，
         *   所以当我们尝试将它设置为空字符串时，
         *   浏览器会将它的值矫正为布尔类型的值，即 false。
         * - <button disabled>Button</button>
         *   el.disabled = ''  ==>  el.disabled = false
         * 
         * 无论是使用 setAttribute 函数，还是直接设置元素的 DOM Properties，都存在缺陷
         * - 优先设置元素的 DOM Properties，但当值为空字符串时，要手动将值矫正为true
         * - 如果 vnode.props 中的属性不具有对应的DOM Properties，
         *   则仍然使用 setAttribute 函数完成属性的设置。
         * */ 

        
         function createRenderer(options) {
            // 通过 options 得到操作 DOM 的 API
            const {
                createElement,
                insert,
                setElementText
            } = options

            // 在这个作用域内定义的函数都可以访问那些 API

            function mountElement(vnode, container) {
                // 调用 createElement 函数创建元素
                const el = createElement(vnode.type)
                
                if (typeof vnode.children === 'string') {
                    // 调用 setElementText 设置元素的文本节点
                    setElementText(el, vnode.children)
                }else if (Array.isArray(vnode.children)) {
                    // 如果 children 是数组，则遍历每一个子节点，并调用 patch 函数挂载它们
                    vnode.children.forEach(child => {
                        patch(null, child, el)
                    })
                }

                // 如果 vnode.props 存在才处理它
                if (vnode.props) {
                    // 遍历 vnode.props
                    for (const key in vnode.props) {
                        // 用 in 操作符判断 key 是否存在对应的 DOM Properties
                        if (key in el) { // 新增
                            // 获取该 DOM Properties 的类型
                            const type = typeof el[key]
                            const value = vnode.props[key]
                            // 如果是布尔类型，并且 value 是空字符串，则将值矫正为 true
                            if (type === 'boolean' && value === '') {
                                el[key] = true
                            } else {
                                el[key] = value
                            }
                        } else {
                            // 如果要设置的属性没有对应的 DOM Properties，则使用 setAttribute 函数设置属性
                            el.setAttribute(key, vnode.props[key])
                        }
                    }
                }

                // 调用 insert 函数将元素插入到容器内
                insert(el, container)
            }

            // 第一个参数 n1 代表旧 vnode，第二个参数 n2 代表新 vnode。
            function patch(n1, n2, container) {
                // 如果 n1 不存在，意味着挂载，则调用 mountElement 函数完成挂载
                if (!n1) {
                    mountElement(n2, container)
                } else {
                    // n1 存在，意味着打补丁，暂时省略
                }
            }

            function render(vnode, container) {
                if (vnode) {
                    // 新 vnode 存在，将其与旧 vnode 一起传递给 patch 函数，进行打补丁
                    patch(container._vnode, vnode, container)
                } else {
                    if (container._vnode) {
                        // 旧 vnode 存在，且新 vnode 不存在，说明是卸载（unmount）操作
                        // 只需要将 container 内的 DOM 清空即可
                        container.innerHTML = ''
                    }
                }
                // 把 vnode 存储到 container._vnode 下，即后续渲染中的旧 vnode
                container._vnode = vnode
            }

            return {
                render
            }
        }

        // 创建一个渲染器
        // 在创建 renderer 时传入配置项
        const renderer = createRenderer({
            // 用于创建元素
            createElement(tag) {
                return document.createElement(tag)
            },
            // 用于设置元素的文本节点
            setElementText(el, text) {
                el.textContent = text
            },
            // 用于在给定的 parent 下添加指定元素
            insert(el, parent, anchor = null) {
                parent.insertBefore(el, anchor)
            }
        })

        // 新增 测试
        const vnode = {
            type: 'button',
            props: {
                disabled: '',
            },
            children: 'button'
        }

        // 调用 render 函数渲染该 vnode
        renderer.render(vnode, document.querySelector('#foo'))

    </script>
    

</html>